﻿using System;
using System.Diagnostics;
using System.Globalization;
using System.Runtime.InteropServices;
using System.ComponentModel.Design;
using Microsoft.Win32;
using Microsoft.VisualStudio;
using Microsoft.VisualStudio.Shell.Interop;
using Microsoft.VisualStudio.OLE.Interop;
using Microsoft.VisualStudio.Shell;
using EnvDTE;
using System.Collections.Generic;
using System.Xml;

namespace DbmlUpdater
{
	/// <summary>
	/// This is the class that implements the package exposed by this assembly.
	///
	/// The minimum requirement for a class to be considered a valid package for Visual Studio
	/// is to implement the IVsPackage interface and register itself with the shell.
	/// This package uses the helper classes defined inside the Managed Package Framework (MPF)
	/// to do it: it derives from the Package class that provides the implementation of the 
	/// IVsPackage interface and uses the registration attributes defined in the framework to 
	/// register itself and its components with the shell.
	/// </summary>
	// This attribute tells the PkgDef creation utility (CreatePkgDef.exe) that this class is
	// a package.
	[PackageRegistration(UseManagedResourcesOnly = true)]
	// This attribute is used to register the informations needed to show the this package
	// in the Help/About dialog of Visual Studio.
	//[InstalledProductRegistration("#110", "#112", "1.0", IconResourceID = 400)]
	// This attribute is needed to let the shell know that this package exposes some menus.
	[ProvideMenuResource("Menus.ctmenu", 1)]
	[Guid(GuidList.guidDbmlUpdaterPkgString)]
	[ProvideAutoLoad("{6a228c6d-ca22-4d89-8d01-add213cb4d13}")]
	public sealed class DbmlUpdaterPackage : Package, IOleCommandTarget
	{
		/// <summary>
		/// Default constructor of the package.
		/// Inside this method you can place any initialization code that does not require 
		/// any Visual Studio service because at this point the package object is created but 
		/// not sited yet inside Visual Studio environment. The place to do all the other 
		/// initialization is the Initialize method.
		/// </summary>
		public DbmlUpdaterPackage()
		{
			Trace.WriteLine(string.Format(CultureInfo.CurrentCulture, "Entering constructor for: {0}", this.ToString()));
		}



		/////////////////////////////////////////////////////////////////////////////
		// Overriden Package Implementation
		#region Package Members

		/// <summary>
		/// Initialization of the package; this method is called right after the package is sited, so this is the place
		/// where you can put all the initilaization code that rely on services provided by VisualStudio.
		/// </summary>
		protected override void Initialize()
		{
			Trace.WriteLine(string.Format(CultureInfo.CurrentCulture, "Entering Initialize() of: {0}", this.ToString()));
			base.Initialize();

			// Add our command handlers for menu (commands must exist in the .vsct file)
			OleMenuCommandService mcs = GetService(typeof(IMenuCommandService)) as OleMenuCommandService;
			if (null != mcs)
			{
				// Create the command for the menu item.
				CommandID menuCommandID = new CommandID(GuidList.guidDbmlUpdaterCmdSet, (int)PkgCmdIDList.DBMLUpdaterRunCmd);

				OleMenuCommand menuItem = new OleMenuCommand(MenuItemCallback, menuCommandID);
				mcs.AddCommand(menuItem);

			}

		}

		#endregion

		private IVsSolution solution;
		private System.Threading.Thread workerThread;

		/// <summary>
		/// This function is the callback used to execute a command when the a menu item is clicked.
		/// See the Initialize method to see how the menu item is associated to this function using
		/// the OleMenuCommandService service and the MenuCommand class.
		/// </summary>
		private void MenuItemCallback(object sender, EventArgs e)
		{
			try
			{
				OleMenuCommand mc = sender as OleMenuCommand;

				IVsOutputWindowPane outputPane = CreatePane(GuidList.guidOutputPane, "DBML Updater", true, true);

				outputPane.Activate();

				if (workerThread != null && workerThread.IsAlive)
				{
					System.Windows.Forms.DialogResult result = System.Windows.Forms.MessageBox.Show(@"DBML Updater already running. Process will be stopped.

If you want to start process again, click Yes. Otherwise click No or click on Cancel and keep the process running.", "DBML Updater", System.Windows.Forms.MessageBoxButtons.YesNoCancel, System.Windows.Forms.MessageBoxIcon.Question);

					if (result == System.Windows.Forms.DialogResult.Cancel)
						return;

					workerThread.Abort();

					if (result == System.Windows.Forms.DialogResult.No)
						return;
				}

				outputPane.Clear();

				workerThread = new System.Threading.Thread(new System.Threading.ParameterizedThreadStart(Run));

				workerThread.Start(outputPane);


				/*
				Worker w = new Worker(s => outputPane.OutputString(s));

				if (w.CheckWinSDK())
				{
					outputPane.OutputString(" Searching for .config files...");
				
					solution = this.GetService(typeof(SVsSolution)) as IVsSolution;
					list.Clear();

					EnumHierarchyItems(solution as IVsHierarchy, VSConstants.VSITEMID_ROOT, 0, true, false, FindConfigs, null, 0);

					if (list.Count == 0)
					{
						outputPane.OutputString("no files found\n");
					}
					else
					{
						outputPane.OutputString("OK\n\n");

						foreach (var item in list)
						{
							string codeFileName;
							w.Generate(item.FileName, out codeFileName);
							System.Windows.Forms.MessageBox.Show(codeFileName);
						}
					}

				}
				*/
				outputPane.OutputString(" ------------------------------------------------------------------------------ \n");
			}
			catch (Exception ex)
			{
				System.Windows.Forms.MessageBox.Show(ex.ToString(), "MenuCommand");
			}
		}

		public void Run(object o)
		{
			DTE dte = GetService(typeof(SDTE)) as DTE;
			StatusBar statusBar = dte.StatusBar;
			statusBar.Progress(true, "DBML Updater", 0, 1);
			statusBar.Animate(true, vsStatusAnimation.vsStatusAnimationGeneral);
			IVsOutputWindowPane outputPane = o as IVsOutputWindowPane;

			try
			{
				Worker w = new Worker(s => outputPane.OutputString(s), sb => statusBar.Text = "DBML Updater: " + sb);

				if (w.CheckWinSDK())
				{
					outputPane.OutputString(" Searching for .config files...");
					statusBar.Text = "DBML Updater: Searching for .config files...";

					solution = this.GetService(typeof(SVsSolution)) as IVsSolution;
					list.Clear();

					EnumHierarchyItems(solution as IVsHierarchy, VSConstants.VSITEMID_ROOT, 0, true, false, FindConfigs, null, 0);

					if (list.Count == 0)
					{
						outputPane.OutputString("no files found\n");
						statusBar.Text = "DBML Updater: Searching for .config files...no files found";
					}
					else
					{
						outputPane.OutputString("OK\n\n");
						statusBar.Text = "DBML Updater: Searching for .config files...OK";

						foreach (var item in list)
						{
							string codeFileName;
							w.Generate(item.FileName, out codeFileName);
							if (!string.IsNullOrEmpty(codeFileName))
							{
								//item.Project.AddItem(, VSADDITEMOPERATION.VSADDITEMOP_LINKTOFILE
								int pfFound;
								VSDOCUMENTPRIORITY[] pdwPriority = new VSDOCUMENTPRIORITY[1];
								uint pitemid;
								if (item.Project.IsDocumentInProject(codeFileName, out pfFound, pdwPriority, out pitemid) == VSConstants.S_OK)
								{
									if (pfFound == 0)
									{
										VSADDRESULT[] pResult = new VSADDRESULT[1];
										int r = item.Project.AddItem(item.ParentItemid, VSADDITEMOPERATION.VSADDITEMOP_OPENFILE, System.IO.Path.GetFileName(codeFileName), 1, new string[] { codeFileName }, IntPtr.Zero, pResult);
										if (r == VSConstants.S_OK)
										{

										}
									}
								}
							}
							//System.Windows.Forms.MessageBox.Show(codeFileName);
						}
					}

				}
			}
			catch (System.Threading.ThreadAbortException)
			{

			}
			catch (Exception ex)
			{
				System.Windows.Forms.MessageBox.Show(ex.ToString(), "Run");
			}
			finally
			{
				statusBar.Clear();
				statusBar.Animate(false, vsStatusAnimation.vsStatusAnimationGeneral);
			}
		}

		private IVsOutputWindowPane CreatePane(Guid paneGuid, string title, bool visible, bool clearWithSolution)
		{
			IVsOutputWindow output = (IVsOutputWindow)GetService(typeof(SVsOutputWindow));
			IVsOutputWindowPane pane;

			// Create a new pane.
			output.CreatePane(ref paneGuid, title, Convert.ToInt32(visible), Convert.ToInt32(clearWithSolution));

			// Retrieve the new pane.
			output.GetPane(ref paneGuid, out pane);

			return pane;
		}

		private IEnumerable<IVsProject> LoadedProjects
		{
			get
			{
				IVsSolution solution = this.GetService(typeof(SVsSolution)) as IVsSolution;
				IEnumHierarchies enumerator = null;
				Guid guid = Guid.Empty;
				solution.GetProjectEnum((uint)__VSENUMPROJFLAGS.EPF_LOADEDINSOLUTION, ref guid, out enumerator);
				IVsHierarchy[] hierarchy = new IVsHierarchy[1] { null };
				uint fetched = 0;
				for (enumerator.Reset(); enumerator.Next(1, hierarchy, out fetched) == VSConstants.S_OK && fetched == 1; /*nothing*/)
				{
					//string s;

					//if (solution.GetUniqueNameOfProject(hierarchy[0], out s) == VSConstants.S_OK)
					//  yield return s;

					yield return (IVsProject)hierarchy[0];
				}
			}
		}

		//-----------------------------------------------------------------------------------------------------------------------------------------------

		private static List<ConfigInfo> list = new List<ConfigInfo>();

		struct ConfigInfo
		{
			public string FileName { get; set; }
			public IVsProject Project { get; set; }
			public IVsHierarchy Parent { get; set; }
			public uint ParentItemid { get; set; }
		}

		/*
		/// <summary>
		/// This function diplays the name of the Hierarchy node. This function is passed to the 
		/// Hierarchy enumeration routines to process the current node.
		/// </summary>
		/// <param name="hierarchy">Hierarchy of the current node</param>
		/// <param name="itemid">Itemid of the current node</param>
		/// <param name="recursionLevel">Depth of recursion in hierarchy enumeration. We add one tab
		/// for each level in the recursion.</param>
		private void DisplayHierarchyNode(IVsHierarchy hierarchy, uint itemid, int recursionLevel)
		{
			object pVar;
			int hr;

			string text = "";

			for (int i = 0; i < recursionLevel; i++)
				text += "\t";

			//Get the name of the root node in question here and dump its value
			hr = hierarchy.GetProperty(itemid, (int)__VSHPROPID.VSHPROPID_Name, out pVar);
			text += (string)pVar;


			hr = hierarchy.GetProperty(itemid, (int)__VSHPROPID.VSHPROPID_ParentHierarchyItemid, out pVar); // 
			if (pVar != null)
			{
				uint i = GetItemId(pVar);
				hr = hierarchy.GetProperty(itemid, (int)__VSHPROPID.VSHPROPID_ParentHierarchy, out pVar); // 
				hr = ((IVsHierarchy)pVar).GetProperty(i, (int)__VSHPROPID.VSHPROPID_Name, out pVar);
				text += " - " + (string)pVar;
			}
		}
		*/
		private void FindConfigs(IVsHierarchy hierarchy, uint itemid, int recursionLevel, IVsHierarchy parent, uint parentItemid)
		{
			try
			{

				object pVar;
				int hr;
				string s;

				if (hierarchy.GetProperty(itemid, (int)__VSHPROPID.VSHPROPID_Name, out pVar) != VSConstants.S_OK)
					return;

				string fileName = (string)pVar;

				if (!fileName.EndsWith(".config"))
					return;

				string proj;
				solution.GetProjrefOfItem(hierarchy, itemid, out proj);
				string[] projFrags = proj.Split('|');

				if (!System.IO.File.Exists(projFrags[2]))
					return;

				string xmlRoot = null;
				using (XmlTextReader xml = new XmlTextReader(projFrags[2]))
				{
					while (xml.Read())
					{
						// first element is the root element
						if (xml.NodeType == XmlNodeType.Element)
						{
							xmlRoot = xml.Name;
							break;
						}
					}
				}

				if (!"DBMLUpdater".Equals(xmlRoot, StringComparison.CurrentCultureIgnoreCase))
					return;


				// parent	name
				hr = parent.GetProperty(parentItemid, (int)__VSHPROPID.VSHPROPID_Name, out pVar);

				// project
				IVsHierarchy projectHierarchy;
				hr = solution.GetProjectOfProjref(proj, out projectHierarchy, out s, new VSUPDATEPROJREFREASON[] { VSUPDATEPROJREFREASON.UPR_NoUpdate });
				IVsProject project = (IVsProject)projectHierarchy;



				//list.Add(projFrags[2] + " - " + projFrags[0] + " - " + (string)pVar);
				list.Add(new ConfigInfo() { FileName = projFrags[2], Project = project, Parent = parent, ParentItemid = parentItemid });
			}
			catch (Exception ex)
			{
				System.Windows.Forms.MessageBox.Show(ex.ToString(), "FindConfigs");
			}
		}

		/// <summary>
		/// Enumerates over the hierarchy items for the given hierarchy traversing into nested hierarchies.
		/// </summary>
		/// <param name="hierarchy">hierarchy to enmerate over.</param>
		/// <param name="itemid">item id of the hierarchy</param>
		/// <param name="recursionLevel">Depth of recursion. e.g. if recursion started with the Solution
		/// node, then : Level 0 -- Solution node, Level 1 -- children of Solution, etc.</param>
		/// <param name="hierIsSolution">true if hierarchy is Solution Node. This is needed to special
		/// case the children of the solution to work around a bug with VSHPROPID_FirstChild and 
		/// VSHPROPID_NextSibling implementation of the Solution.</param>
		/// <param name="visibleNodesOnly">true if only nodes visible in the Solution Explorer should
		/// be traversed. false if all project items should be traversed.</param>
		/// <param name="processNodeFunc">pointer to function that should be processed on each
		/// node as it is visited in the depth first enumeration.</param>
		private void EnumHierarchyItems(IVsHierarchy hierarchy, uint itemid, int recursionLevel, bool hierIsSolution, bool visibleNodesOnly, Action<IVsHierarchy, uint, int, IVsHierarchy, uint> processNodeFunc, IVsHierarchy parent, uint parentItemid) //ProcessHierarchyNode processNodeFunc
		{
			int hr;
			IntPtr nestedHierarchyObj;
			uint nestedItemId;
			Guid hierGuid = typeof(IVsHierarchy).GUID;

			// Check first if this node has a nested hierarchy. If so, then there really are two 
			// identities for this node: 1. hierarchy/itemid 2. nestedHierarchy/nestedItemId.
			// We will recurse and call EnumHierarchyItems which will display this node using
			// the inner nestedHierarchy/nestedItemId identity.
			hr = hierarchy.GetNestedHierarchy(itemid, ref hierGuid, out nestedHierarchyObj, out nestedItemId);
			if (VSConstants.S_OK == hr && IntPtr.Zero != nestedHierarchyObj)
			{
				IVsHierarchy nestedHierarchy = Marshal.GetObjectForIUnknown(nestedHierarchyObj) as IVsHierarchy;
				Marshal.Release(nestedHierarchyObj);    // we are responsible to release the refcount on the out IntPtr parameter
				if (nestedHierarchy != null)
				{
					// Display name and type of the node in the Output Window
					EnumHierarchyItems(nestedHierarchy, nestedItemId, recursionLevel, false, visibleNodesOnly, processNodeFunc, hierarchy, itemid);
				}
			}
			else
			{
				object pVar;

				// Display name and type of the node in the Output Window
				processNodeFunc(hierarchy, itemid, recursionLevel, parent, parentItemid);

				recursionLevel++;

				//Get the first child node of the current hierarchy being walked
				// NOTE: to work around a bug with the Solution implementation of VSHPROPID_FirstChild,
				// we keep track of the recursion level. If we are asking for the first child under
				// the Solution, we use VSHPROPID_FirstVisibleChild instead of _FirstChild. 
				// In VS 2005 and earlier, the Solution improperly enumerates all nested projects
				// in the Solution (at any depth) as if they are immediate children of the Solution.
				// Its implementation _FirstVisibleChild is correct however, and given that there is
				// not a feature to hide a SolutionFolder or a Project, thus _FirstVisibleChild is 
				// expected to return the identical results as _FirstChild.
				hr = hierarchy.GetProperty(itemid, ((visibleNodesOnly || (hierIsSolution && recursionLevel == 1) ? (int)__VSHPROPID.VSHPROPID_FirstVisibleChild : (int)__VSHPROPID.VSHPROPID_FirstChild)), out pVar);
				Microsoft.VisualStudio.ErrorHandler.ThrowOnFailure(hr);
				if (VSConstants.S_OK == hr)
				{
					//We are using Depth first search so at each level we recurse to check if the node has any children
					// and then look for siblings.
					uint childId = GetItemId(pVar);
					while (childId != VSConstants.VSITEMID_NIL)
					{
						EnumHierarchyItems(hierarchy, childId, recursionLevel, false, visibleNodesOnly, processNodeFunc, hierarchy, itemid);
						// NOTE: to work around a bug with the Solution implementation of VSHPROPID_NextSibling,
						// we keep track of the recursion level. If we are asking for the next sibling under
						// the Solution, we use VSHPROPID_NextVisibleSibling instead of _NextSibling. 
						// In VS 2005 and earlier, the Solution improperly enumerates all nested projects
						// in the Solution (at any depth) as if they are immediate children of the Solution.
						// Its implementation   _NextVisibleSibling is correct however, and given that there is
						// not a feature to hide a SolutionFolder or a Project, thus _NextVisibleSibling is 
						// expected to return the identical results as _NextSibling.
						hr = hierarchy.GetProperty(childId, ((visibleNodesOnly || (hierIsSolution && recursionLevel == 1)) ? (int)__VSHPROPID.VSHPROPID_NextVisibleSibling : (int)__VSHPROPID.VSHPROPID_NextSibling), out pVar);
						if (VSConstants.S_OK == hr)
						{
							childId = GetItemId(pVar);
						}
						else
						{
							Microsoft.VisualStudio.ErrorHandler.ThrowOnFailure(hr);
							break;
						}
					}
				}
			}
		}


		/// <summary>
		/// Gets the item id.
		/// </summary>
		/// <param name="pvar">VARIANT holding an itemid.</param>
		/// <returns>Item Id of the concerned node</returns>
		private static uint GetItemId(object pvar)
		{
			if (pvar == null) return VSConstants.VSITEMID_NIL;
			if (pvar is int) return (uint)(int)pvar;
			if (pvar is uint) return (uint)pvar;
			if (pvar is short) return (uint)(short)pvar;
			if (pvar is ushort) return (uint)(ushort)pvar;
			if (pvar is long) return (uint)(long)pvar;
			return VSConstants.VSITEMID_NIL;
		}

	}
}
